﻿// ----------------------------------------------------------------------------------
// Sample Code from Fernando Machado Píriz's blog: http://fernandomachadopiriz.com
//
// <copyright company="Fernando Machado Píriz" file="DesktopIconsHelper.cs">
// Copyright (c) Fernando Machado Píriz. Distributed under FSF's GPL v2.</copyright>
// ----------------------------------------------------------------------------------
// This code and information are provided "as is" without warranty of any kind,
// either expressed of implied, including but not limited to the implied warranties
// of merchantability and/or fitness for a particular purpose.
// ----------------------------------------------------------------------------------
namespace DesktopIcons
{
    using System;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Globalization;
    using System.Runtime.InteropServices;
    using System.Text;
    using Microsoft.Win32;
    
    /// <summary>
    /// Provides access to visibility of desktop icons.
    /// </summary>
    public static class DesktopIconsHelper
    {
        /// <summary>
        /// The handle of the window containing desktop icons assigned during window enumeration in <code>FindShellWindow</code>.
        /// </summary>
        private static IntPtr shellWindowHandle;

        /// <summary>
        /// Gets or sets a value indicating whether desktop icons are currently visible.
        /// </summary>
        public static bool Visible
        {
            get
            {
                return AreDesktopIconsVisible();
            }

            set
            {
                ShowDesktopIcons(value);
            }
        }

        /// <summary>
        /// Shows or hides desktop icons depending on the given value.
        /// </summary>
        /// <param name="visible">true to show desktop icons; false otherwise.</param>
        private static void ShowDesktopIcons(bool visible)
        {
            if ((!visible && AreDesktopIconsVisible()) || (visible && !AreDesktopIconsVisible()))
            {
                ToggleDesktopIconsVisibility();
            }
        }

        /// <summary>
        /// Returns desktop icons visibility by reading a Windows Registry entry.
        /// </summary>
        /// <returns>Returns true if desktop icons are visible; returns false otherwise.</returns>
        private static bool AreDesktopIconsVisible()
        {
            bool result = true;
            RegistryKey hkcu = Registry.CurrentUser;
            RegistryKey desktopKey = hkcu.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced");
            try
            {
                object value = desktopKey.GetValue("HideIcons");
                if (value != null)
                {
                    result = !Convert.ToBoolean(value, CultureInfo.InvariantCulture);
                }
            }
            finally
            {
                desktopKey.Close();
            }

            return result;
        }

        /// <summary>
        /// Toggles desktop icons visibility by sending a low level command to desktop window in the same way as the popup menu "Show desktop
        /// icons" does. Use <code>AreDesktopIconsVisible</code> to get current desktop icons visibility status.
        /// </summary>
        private static void ToggleDesktopIconsVisibility()
        {
            IntPtr defaultViewHandle = FindShellWindow();
            Debug.WriteLine("ToggleDesktopIconsVisibility.defaultViewHandle: {0}", defaultViewHandle);

            UIntPtr resultArgument;
            IntPtr returnValue = NativeMethods.SendMessageTimeout(
                defaultViewHandle, 
                NativeMethods.WM_COMMAND, 
                new IntPtr(29698), 
                IntPtr.Zero,
                NativeMethods.SendMessageTimeoutFlags.SMTO_ABORTIFHUNG,
                1000, 
                out resultArgument);

            Debug.WriteLine("ToggleDesktopIconsVisibility.resultArgument: {0}", resultArgument);
            Debug.WriteLine("ToggleDesktopIconsVisibility.returnValue: {0}", returnValue);
        }

        /// <summary>
        /// Called by EnumWindows. Sets <code>shellWindowHandle</code> if a window with class "SHELLDLL_DefView" is found during enumeration.
        /// </summary>
        /// <param name="handle">The handle of the window being enumerated.</param>
        /// <param name="param">The argument passed to <code>EnumWindowsProc</code>; not used in this application.</param>
        /// <returns>Allways returns 1.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
            Justification = "All exceptions are caught to prevent enumeration stop")]
        private static int EnumWindowsProc(IntPtr handle, int param)
        {
            try
            {
                IntPtr foundHandle = NativeMethods.FindWindowEx(handle, IntPtr.Zero, "SHELLDLL_DefView", null);
                if (!foundHandle.Equals(IntPtr.Zero))
                {
                    shellWindowHandle = foundHandle;
                    return 0;
                }
            }
            catch
            {
                // Intentionally left blank
            }

            return 1;
        }

        /// <summary>
        /// Finds the window containing desktop icons.
        /// </summary>
        /// <returns>The handle of the window.</returns>
        private static IntPtr FindShellWindow()
        {
            IntPtr progmanHandle;
            IntPtr defaultViewHandle = IntPtr.Zero;
            IntPtr workerWHandle;
            int errorCode = NativeMethods.ERROR_SUCCESS;

            // Try the easy way first. "SHELLDLL_DefView" is a child window of "Progman".
            progmanHandle = NativeMethods.FindWindowEx(IntPtr.Zero, IntPtr.Zero, "Progman", null);
            Debug.WriteLine("FindShellWindow.progmanHandle: {0}", progmanHandle);

            if (!progmanHandle.Equals(IntPtr.Zero))
            {
                defaultViewHandle = NativeMethods.FindWindowEx(progmanHandle, IntPtr.Zero, "SHELLDLL_DefView", null);
                errorCode = Marshal.GetLastWin32Error();
                Debug.WriteLine("FindShellWindow.defaultViewHandle: {0}", defaultViewHandle);
            }

            if (!defaultViewHandle.Equals(IntPtr.Zero))
            {
                return defaultViewHandle;
            }
            else if (errorCode != NativeMethods.ERROR_SUCCESS)
            {
                // Marshal.ThrowExceptionForHR(errorCode);
                Debug.WriteLine(new Win32Exception(errorCode).Message);
            }

            // Try the not so easy way then. In some systems "SHELLDLL_DefView" is a child of "WorkerW".
            errorCode = NativeMethods.ERROR_SUCCESS;
            workerWHandle = NativeMethods.FindWindowEx(IntPtr.Zero, IntPtr.Zero, "WorkerW", null);
            Debug.WriteLine("FindShellWindow.workerWHandle: {0}", workerWHandle);

            if (!workerWHandle.Equals(IntPtr.Zero))
            {
                defaultViewHandle = NativeMethods.FindWindowEx(workerWHandle, IntPtr.Zero, "SHELLDLL_DefView", null);
                errorCode = Marshal.GetLastWin32Error();
                Debug.WriteLine("FindShellWindow.defaultViewHandle: {0}", defaultViewHandle);
            }

            if (!defaultViewHandle.Equals(IntPtr.Zero))
            {
                return defaultViewHandle;
            }
            else if (errorCode != NativeMethods.ERROR_SUCCESS)
            {
                // Marshal.ThrowExceptionForHR(errorCode);
                Debug.WriteLine(new Win32Exception(errorCode).Message);
            }

            shellWindowHandle = IntPtr.Zero;

            // Try the hard way. In some systems "SHELLDLL_DefView" is a child or a child of "Progman".
            if (NativeMethods.EnumWindows(EnumWindowsProc, progmanHandle) == 0)
            {
                errorCode = Marshal.GetLastWin32Error();
                if (errorCode != NativeMethods.ERROR_SUCCESS)
                {
                    // Marshal.ThrowExceptionForHR(errorCode);
                    Debug.WriteLine(new Win32Exception(errorCode).Message);
                }
            }

            // Try the even more harder way. Just in case "SHELLDLL_DefView" is in another desktop.
            if (shellWindowHandle.Equals(IntPtr.Zero))
            {
                if (NativeMethods.EnumDesktopWindows(IntPtr.Zero, EnumWindowsProc, progmanHandle))
                {
                    errorCode = Marshal.GetLastWin32Error();
                    if (errorCode != NativeMethods.ERROR_SUCCESS)
                    {
                        // Marshal.ThrowExceptionForHR(errorCode);
                        Debug.WriteLine(new Win32Exception(errorCode).Message);
                    }
                }
            }

            Debug.WriteLine("FindShellWindow.shellWindowHandle: {0}", shellWindowHandle);
            return shellWindowHandle;
        }

        /// <summary>
        /// Win32 API imports.
        /// </summary>
        private static class NativeMethods
        {
            /// <summary>
            /// The action completed successfully.
            /// </summary>
            public const int ERROR_SUCCESS = 0x0;

            /// <summary>
            /// The WM_COMMAND message is sent when the user selects a command item from a menu, when a control sends a notification message to
            /// its parent window, or when an accelerator keystroke is translated. 
            /// </summary>
            public const uint WM_COMMAND = 0x111;

            /// <summary>
            /// Delegate used to call <code>EnumWindowsProc</code>.
            /// </summary>
            /// <param name="handle">Handle received during window enumeration.</param>
            /// <param name="param">Parameter passed to <code>EnumWindowsProc</code>; not used in this application.</param>
            /// <returns>1 to continue windows enumeration; 0 otherwise.</returns>
            public delegate int EnumWindowsProcDelegate(IntPtr handle, int param);

            /// <summary>
            /// The behavior of the SendMessageTimeout. This parameter can be one or more of the following values.
            /// </summary>
            [Flags]
            public enum SendMessageTimeoutFlags : uint
            {
                /// <summary>
                /// The calling thread is not prevented from processing other requests while waiting for the function to return.
                /// </summary>
                SMTO_NORMAL = 0x0,

                /// <summary>
                /// Prevents the calling thread from processing any other requests until the function returns.
                /// </summary>
                SMTO_BLOCK = 0x1,

                /// <summary>
                /// The function returns without waiting for the time-out period to elapse if the receiving thread appears to not respond or
                /// "hangs."
                /// </summary>
                SMTO_ABORTIFHUNG = 0x2,

                /// <summary>
                /// The function does not enforce the time-out period as long as the receiving thread is processing messages.
                /// </summary>
                SMTO_NOTIMEOUTIFNOTHUNG = 0x8
            }

            [DllImport("user32.dll")]
            public static extern int EnumWindows(EnumWindowsProcDelegate lpEnumFunc, IntPtr lParam);

            [DllImport("user32.dll")]
            [return:MarshalAs(UnmanagedType.Bool)]
            public static extern bool EnumDesktopWindows(IntPtr hDesktop, EnumWindowsProcDelegate lpEnumFunc, IntPtr lParam);

            [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
            public static extern IntPtr FindWindowEx(
                IntPtr hwndParent, 
                IntPtr hwndChildAfter,
                [MarshalAs(UnmanagedType.LPWStr)]string lpszClass, 
                [MarshalAs(UnmanagedType.LPWStr)]string lpszWindow);

            [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
            public static extern IntPtr SendMessageTimeout(
                IntPtr hWnd, 
                uint Msg, 
                IntPtr wParam, 
                IntPtr lParam,
                SendMessageTimeoutFlags fuFlags, 
                uint uTimeout, 
                out UIntPtr lpdwResult);
        }
    }
}
